Skip to content

feat(devtools)!: reuse the built-in Terminals dock via ctx.terminals#1026

Open
antfubot wants to merge 2 commits into
nuxt:mainfrom
antfubot:feat/terminals-reuse-devframe
Open

feat(devtools)!: reuse the built-in Terminals dock via ctx.terminals#1026
antfubot wants to merge 2 commits into
nuxt:mainfrom
antfubot:feat/terminals-reuse-devframe

Conversation

@antfubot

@antfubot antfubot commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements plan 02-terminals-reuse from the Vite DevTools integration series. Nuxt DevTools stops shipping its own @xterm terminal UI + RPC and instead surfaces terminal sessions through the devframe terminals host (ctx.terminals), so they render in Vite DevTools' built-in Terminals dock (which auto-hides when empty). The Nuxt "Terminals" tab is removed.

Existing modules keep working via a compat shim, and a new opt-in interactive PTY path is added.

Rebased on main after the Vite DevTools Kit 0.4.2 / devframe 0.7.5 upgrade (#1029) and the Messages unification (#1025), and updated to adopt the new devframe capabilities.

What changed

Server bridge (server-rpc/terminals.ts, rewritten)

  • Bridges the devtools:terminal:* hooks onto ctx.terminals instead of maintaining its own Map + RPC:
    • register → a read-only DevframeTerminalSession carrying a ReadableStream (registered via ctx.terminals.register). Re-registration resets/clears in place, so startSubprocess().clear()/restart() still work.
    • writecontroller.enqueue(data); exit → close stream + update(status); remove → dispose + remove the session.
    • New devtools:terminal:spawn hook → ctx.terminals.startChildProcess (output-only) or ctx.terminals.startPtySession (interactive PTY, zigpty with graceful pipe fallback).
  • Terminals registered before the kit connects are buffered and replayed inside the devtools:ready callback.

devframe 0.7 capability — docks.activate({ sessionId })

  • Added a revealTerminal(id) RPC that calls ctx.docks.activate('devframes_plugin_terminals', { sessionId }) to focus the built-in Terminals dock on a specific session.
  • The in-client "reveal terminal" affordances (installing-module card, "Building…" button, npm update) now focus the dock on the right session instead of navigating to the removed tab — a UX upgrade over simply dropping the links.

Kit surface (@nuxt/devtools-kit)

  • Added SpawnTerminalOptions, the devtools:terminal:spawn hook, a startDevToolsTerminal() helper (opt-in PTY), and the revealTerminal RPC type.
  • Dropped getTerminals/getTerminalDetail/runTerminalAction + onTerminalData from the RPC types (kept onTerminalExit). TerminalState/TerminalInfo/TerminalAction and the devtools:terminal:* module-facing hooks are preserved.

Internal producers — module-install / analyze-build / npm-update already spawn via startChildProcess; they now broadcast onTerminalExit on process exit so their transient UI state (installing-modules, "Building…", update spinner) still clears. Exit-driven cleanup is not lost.

Client — deleted the terminals.vue tab, TerminalPage.vue, TerminalView.vue; removed useTerminals, useCurrentTerminalId, and the onTerminalData handler. Removed the @xterm/* deps from package.json, pnpm-workspace.yaml, and the client nuxt.config.ts bundling/optimizeDeps; refreshed the lockfile (the built-in @devframes/plugin-terminals owns xterm now).

Docs — documented startDevToolsTerminal() and refreshed the "Terminals tab" references.

Bug fix — VS Code Server launcher was a silent no-op. While validating this
change against a real dev server, found and fixed a pre-existing bug (from
#966, unrelated to this PR's changes) that broke every module-main.ts
integration relying on ctx.devtoolsKit (VS Code Server, Vue DevTools, Vite
Inspect, Vue Tracer, plugin-metrics, timeline): setupRPC()'s return
statement and module-main.ts's destructuring both spread ctx
({ connectDevToolsKit, ...ctx }), and ctx.devtoolsKit is a getter — object
spread reads a getter's current value into a plain property, which at that
point (synchronous module setup, before the kit connects) is always
undefined. This permanently froze ctx.devtoolsKit to undefined, so e.g.
clicking "Launch" on the VS Code Server tab silently did nothing (its
ctx.devtoolsKit?.terminals.startChildProcess(...) call was always skipped by
the optional chain) — no process spawned, nothing in the Terminals dock.
Fixed by returning/destructuring ctx as a nested property instead of
spreading it. Verified against a real dev server: pre-fix, ctx.devtoolsKit
is undefined at the call site and no process spawns; post-fix it resolves
to the connected kit and a real, tracked code-server child process spawns.

Notes / breaking changes

  • New major: the bespoke Nuxt terminals RPC (getTerminals/getTerminalDetail/runTerminalAction) and the @xterm UI are removed. Module-facing hooks (devtools:terminal:register/write/remove/exit) and startSubprocess() continue to work via the shim.
  • The startSubprocess/getProcess() deprecation codes (NDT_DEP_0004/0001) already shipped; this PR swaps the underlying implementation rather than adding new codes.
  • Unrelated: pnpm typecheck currently reports one pre-existing error in the client/server/api/echo.post.ts fixture that is present on main (surfaced by the deps: upgrade Vite DevTools kit to 0.4.2 #1029 dep bump), not introduced here.

Verification

pnpm lint, pnpm build, and pnpm test:unit pass; pnpm typecheck is clean apart from the pre-existing echo.post.ts fixture error noted above.

This PR was created with the help of an agent.

Retire Nuxt DevTools' bespoke @xterm terminal UI + RPC and surface terminal
sessions through the Vite DevTools terminals host (ctx.terminals), so they
render in the built-in Terminals dock.

Existing modules keep working: the devtools:terminal:* hooks are bridged onto
ctx.terminals as read-only registered sessions (module still owns the process
and streams output). A new opt-in interactive PTY path is added via a
devtools:terminal:spawn hook and the startDevToolsTerminal() helper.

Exit-driven cleanup for the internal module-install / analyze-build / npm-update
flows is preserved by broadcasting terminal exits from their startChildProcess
producers.

Adopts the devframe 0.7 docks.activate({ sessionId }) capability: the in-client
"reveal terminal" affordances (module install, build analyze, npm update) now
focus the built-in Terminals dock on the relevant session via a revealTerminal
RPC, instead of the removed Terminals tab.

Created with the help of an agent.
@antfubot
antfubot force-pushed the feat/terminals-reuse-devframe branch from badf065 to 4f421ba Compare July 21, 2026 08:59
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Terminal support now bridges Nuxt terminal hooks to the built-in DevTools Terminals dock. New spawn options, hooks, RPC contracts, and a public helper support PTY or child-process sessions. Client terminal pages, xterm integration, and route-based navigation are removed; module and build workflows reveal terminals through RPC. Server-side analyze-build and npm operations broadcast process exit codes, while documentation and dependency configuration are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: reusing the built-in Terminals dock through ctx.terminals.
Description check ✅ Passed The description is detailed and directly matches the changeset, including the dock bridge, PTY support, compat shim, and removed custom UI.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/devtools/src/server-rpc/npm.ts (1)

65-78: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Ensure the terminal exit is broadcast even if the session rejects.

If session.getResult() rejects (e.g., due to a failure to spawn the process), the exit is never broadcast. This can leave the client's UI in a perpetual "updating" state. Consider broadcasting a non-zero exit code in the .catch block.

♻️ Proposed fix to broadcast on error
     // Surface the exit to the client so the update UI state can settle.
     void Promise.resolve(session.getResult()).then((result) => {
       broadcastTerminalExit(ctx, processId, result.exitCode)
-    }).catch(() => {})
+    }).catch(() => {
+      broadcastTerminalExit(ctx, processId, 1)
+    })
 
     return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/devtools/src/server-rpc/npm.ts` around lines 65 - 78, Update the
Promise chain around session.getResult() so its catch handler calls
broadcastTerminalExit with processId and a non-zero exit code when the session
rejects. Preserve the existing result.exitCode broadcast for successful
sessions, ensuring every terminal outcome notifies the client.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/devtools/src/server-rpc/npm.ts`:
- Around line 65-78: Update the Promise chain around session.getResult() so its
catch handler calls broadcastTerminalExit with processId and a non-zero exit
code when the session rejects. Preserve the existing result.exitCode broadcast
for successful sessions, ensuring every terminal outcome notifies the client.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 008b91d4-8ba3-471e-8395-83c3a464aeff

📥 Commits

Reviewing files that changed from the base of the PR and between 03b7a1a and 4f421ba.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • docs/content/2.module/1.utils-kit.md
  • packages/devtools-kit/src/_types/hooks.ts
  • packages/devtools-kit/src/_types/rpc.ts
  • packages/devtools-kit/src/_types/terminals.ts
  • packages/devtools-kit/src/index.ts
  • packages/devtools/client/components/ModuleItem.vue
  • packages/devtools/client/components/NpmVersionCheck.vue
  • packages/devtools/client/components/TerminalPage.vue
  • packages/devtools/client/components/TerminalView.vue
  • packages/devtools/client/composables/state-routes.ts
  • packages/devtools/client/composables/state.ts
  • packages/devtools/client/nuxt.config.ts
  • packages/devtools/client/pages/modules/analyze-build.vue
  • packages/devtools/client/pages/modules/modules.vue
  • packages/devtools/client/pages/modules/terminals.vue
  • packages/devtools/client/setup/client-rpc.ts
  • packages/devtools/package.json
  • packages/devtools/src/server-rpc/analyze-build.ts
  • packages/devtools/src/server-rpc/index.ts
  • packages/devtools/src/server-rpc/npm.ts
  • packages/devtools/src/server-rpc/terminals.ts
  • playgrounds/module-starter/playground/nuxt.config.ts
  • pnpm-workspace.yaml
💤 Files with no reviewable changes (9)
  • packages/devtools/client/pages/modules/terminals.vue
  • packages/devtools/client/components/TerminalPage.vue
  • packages/devtools/client/composables/state.ts
  • packages/devtools/client/components/TerminalView.vue
  • packages/devtools/client/setup/client-rpc.ts
  • packages/devtools/package.json
  • packages/devtools/client/composables/state-routes.ts
  • pnpm-workspace.yaml
  • packages/devtools/client/nuxt.config.ts

…ions

`setupRPC()`'s `ctx.devtoolsKit` is a getter backed by a closure variable set
once the Vite DevTools kit connects. Both `setupRPC`'s own return statement
and `module-main.ts`'s destructuring spread `ctx` (`{ connectDevToolsKit,
...ctx }` / `const { ..., ...ctx } = setupRPC(...)`), and object spread reads
a getter's *current* value into a plain property on the new object — at that
point (synchronous module setup, before the kit has connected) the value is
always `undefined`. This permanently froze `ctx.devtoolsKit` to `undefined`
for every integration wired through `module-main.ts` (vscode, vue-devtools,
vite-inspect, vue-tracer, plugin-metrics, timeline), even long after the kit
actually connected.

Concretely, this made the VS Code Server launcher a silent no-op: its
`ctx.devtoolsKit?.terminals.startChildProcess(...)` call was always skipped
by the optional-chain, so clicking "Launch" never spawned `code-server` and
nothing showed up in the Terminals dock.

Fix: return/destructure `ctx` as a nested property instead of spreading it,
so the getter — and therefore the live connected kit — stays intact.

Verified against a real Nuxt dev server: pre-fix, `ctx.devtoolsKit` is
`undefined` at the VS Code integration's call site and no process spawns;
post-fix it resolves to the connected kit and `startChildProcess` returns a
real, tracked child-process session.

Created with the help of an agent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant